package org.openclinic.service.impl;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import org.joda.time.LocalDateTime;
import org.joda.time.format.DateTimeFormat;
import org.openclinic.domain.Appointment;
import org.openclinic.domain.Clinic;
import org.openclinic.domain.ScheduleHeader;
import org.openclinic.exception.DomainException;
import org.openclinic.exception.ErrorCodes;
import org.openclinic.repository.AppointmentRepository;
import org.openclinic.repository.ReportsRepository;
import org.openclinic.service.AppointmentService;
import org.openclinic.to.ReportParametersTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.stereotype.Service;

@Service
public class AppointmentServiceImpl implements AppointmentService {
	
	private static final String[] daysOfWeek = 
		{ "Domingo", "Segunda", "Terça", "Quarta", "Quinta", "Sexta", "Sábado" };

	@Autowired
	private Clinic clinic;
	
	@Autowired
	private AppointmentRepository appointmentRepository;
	
	@Autowired
	private ReportsRepository reportsRepository;
	
	@Override
	public void cancel(long appointmentId) {
		if (!clinic.isOfflineMode()) {
			SimpleDateFormat sdf = new SimpleDateFormat("dd/MM");
			Appointment appointment = find(appointmentId);
			SimpleMailMessage mailMessage = new SimpleMailMessage();
			mailMessage.setFrom("noreply@openclinic.org");
			mailMessage.setTo(appointment.getCustomer().getEmail());
			mailMessage.setSentDate(new Date());
			mailMessage.setSubject("Sua consulta médica em " + sdf.format(appointment.getStartDate()) + " foi cancelada");
			mailMessage.setText("Para maiores informações entre em contato através do telefone: " + clinic.getPhone());
		}
		
		appointmentRepository.cancelByAppointmentId(appointmentId);
	}
	
	@Override
	public Appointment find(long appointmentId) {
		return appointmentRepository.findByAppointmentId(appointmentId);
	}
	
	@Override
	public Appointment find(long professionalId, long customerId) {
		LocalDateTime startDate = new LocalDateTime().withHourOfDay(0).withMinuteOfHour(0).withSecondOfMinute(0);
		LocalDateTime endDate = new LocalDateTime().withHourOfDay(23).withMinuteOfHour(59).withSecondOfMinute(59);
		List<Appointment> todayAppointments = appointmentRepository.find(professionalId, customerId, startDate.toDate(), endDate.toDate());
		if (!todayAppointments.isEmpty()) {
			return todayAppointments.get(todayAppointments.size() - 1);
		}
		return null;
	}
	
	@Override
	public int[][][] find(String aDate, long specialtyId, long professionalId, int startHour, int totalBusinessHours) {
		SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
		int[][][] appointments = new int[5][totalBusinessHours][4];
		List<ScheduleHeader> interval = getScheduleHeaders(aDate);
		LocalDateTime startDate = LocalDateTime.parse(aDate + " 00:00", DateTimeFormat.forPattern("dd/MM/yyyy HH:mm"));
		LocalDateTime endDate = LocalDateTime.parse(interval.get(interval.size() - 1).getDate() + " 23:59", DateTimeFormat.forPattern("dd/MM/yyyy HH:mm"));
		List<Appointment> appointmentList = appointmentRepository.findByProfessionalSpecialty(specialtyId, professionalId, startDate.toDate(), endDate.toDate());
		
		for (Appointment appointment : appointmentList) {
			LocalDateTime appointmentStartDate = new LocalDateTime(appointment.getStartDate());
			String stringRepresentation = sdf.format(appointment.getStartDate());
			int targetDay = 0;		
			int targetHour = appointmentStartDate.getHourOfDay() - startHour;
			int targetMinutes = appointmentStartDate.getMinuteOfHour() / 15;
			
			for (; targetDay < interval.size(); targetDay++) {
				if (interval.get(targetDay).getDate().equals(stringRepresentation)) {
					break;
				}
			}
			
			appointments[targetDay][targetHour][targetMinutes] = (int) appointment.getAppointmentId().longValue();
		}
		
		return appointments;
	}
	
	@Override
	public List<Appointment> findByCustomer(long customerId) {
		return appointmentRepository.findByCustomerUserIdAndCanceled(customerId, false);
	}
	
	@Override
	public List<Appointment> findProfessionalTodayAppointments(long professionalId) {
		LocalDateTime startDate = new LocalDateTime().withHourOfDay(0).withMinuteOfHour(0).withSecondOfMinute(0);
		LocalDateTime endDate = new LocalDateTime().withHourOfDay(23).withMinuteOfHour(59).withSecondOfMinute(59);
		return appointmentRepository.findByProfessionalAndDate(professionalId, startDate.toDate(), endDate.toDate());
	}
	
	@Override
	public List<Appointment> getReportResults(ReportParametersTO parameters) {
		return reportsRepository.getReportResults(parameters);
	}
	
	@Override
	public List<ScheduleHeader> getScheduleHeaders(String startDate) {
		SimpleDateFormat sdfDate = new SimpleDateFormat("dd/MM/yyyy");
		SimpleDateFormat sdfDisplay = new SimpleDateFormat("dd/MM");
		List<ScheduleHeader> headers = new ArrayList<ScheduleHeader>();
		
		Calendar calendar = Calendar.getInstance();
		
		if (startDate != null) {
			try {
				calendar.setTime(sdfDate.parse(startDate));
			} catch (ParseException e) {
				e.printStackTrace();
			}			
		}
		
		for (int i = 0; i < 5; i++) {
			
			switch (calendar.get(Calendar.DAY_OF_WEEK)) {
				case Calendar.SUNDAY:
					calendar.add(Calendar.DATE, 1);
					break;
				case Calendar.SATURDAY:
					calendar.add(Calendar.DATE, 2);
					break;
			}
			
			headers.add(
				new ScheduleHeader(
					sdfDate.format(calendar.getTime()),
					daysOfWeek[calendar.get(Calendar.DAY_OF_WEEK) - 1] + ", " + sdfDisplay.format(calendar.getTime()))); 

			calendar.add(Calendar.DATE, 1);
		}
		
		return headers;
	}
	
	@Override
	public Appointment save(Appointment appointment) {
		Date currentDate = new Date();
		
		if (appointment.getAppointmentId() == null) {
			appointment.setCreationDate(currentDate);
		}
		appointment.setEndDate(new LocalDateTime(appointment.getStartDate()).plusMinutes(15).toDate());
		appointment.setLastUpdate(currentDate);
		appointment.setCanceled(false);
		
		verifyProfessionalConflict(appointment);
		verifyCustomerConflict(appointment);
		
		if (!clinic.isOfflineMode()) {
			SimpleMailMessage mailMessage = new SimpleMailMessage();
			mailMessage.setFrom("noreply@openclinic.org");
			mailMessage.setTo(appointment.getCustomer().getEmail());
			mailMessage.setSentDate(currentDate);
			mailMessage.setSubject("Você possui uma consulta médica agendada");
			mailMessage.setText("Para maiores informações entre em contato através do telefone: " + clinic.getPhone());
		}
		
		return appointmentRepository.save(appointment);
	}

	private void verifyCustomerConflict(Appointment appointment) {
		List<Appointment> appointments = appointmentRepository.findByCustomerAndDate(appointment.getCustomer().getUserId(), appointment.getStartDate());
		if (!appointments.isEmpty()) {
			throw new DomainException("appointment", "appointmentDate", ErrorCodes.UNIQUE_VIOLATED + ".customerAppointmentDate", appointment.getStartDate().toString());
		}
	}
	
	private void verifyProfessionalConflict(Appointment appointment) {
		List<Appointment> appointments = appointmentRepository.findByProfessionalAndDate(appointment.getHealthProfessional().getUserId(), appointment.getStartDate());
		if (!appointments.isEmpty()) {
			throw new DomainException("appointment", "appointmentDate", ErrorCodes.UNIQUE_VIOLATED + ".professionalAppointmentDate", appointment.getStartDate().toString());
		}
	}
}
